////////////////////////////////////////////////
// DESCRIPTION:
//    Support for emulation of virtual Serial port interop
//
// Legal Notices:
//   Copyright (c) 2008, Telliam Consulting, LLC.
//   All rights reserved.
//
//   Redistribution and use in source and binary forms, with or without modification,
//   are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice, this list
//     of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice, this
//     list of conditions and the following disclaimer in the documentation and/or other
//     materials provided with the distribution.
//   * Neither the name of Telliam Consulting nor the names of its contributors may be
//     used to endorse or promote products derived from this software without specific
//     prior written permission.
//
//   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
//   EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
//   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
//   SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
//   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
//   BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
//   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
//   ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
//   DAMAGE. 
//
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Collections.Generic;
using Microsoft.SPOT.Emulator.Com;
using FusionWare.SPOT.Runtime.Serialization;

namespace FusionWare.SPOT.EmulatorComponents
{
    ///<summary>Class to emulate serial port based devices</summary>
    ///<remarks>
    /// This class provides support for emulating devices attached via
    /// a serial port. There are many ways of emulating serial ports 
    /// and devices including actually routing data from the emulator
    /// out the real serial port. This particular class provides a means
    /// to capture all the data communication internally within the emulator 
    /// to simulate the data communications. To do this it uses a pair of
    /// <see cref="FusionWare.SPOT.EmulatorComponents.FifoStream"/>
    /// instances sharing a pair of <see cref="T:FusionWare.EmulatorComponents.Fifo`1">Fifo</see>
    /// instances to form a sort of virtual NULL Modem cable between the CLR
    /// stream and the emulated device stream. Thus you can validate and test
    /// actual PC Host side .NET communication libraries that use a stream
    /// for talking to the device within the emulator. 
    ///</remarks>
    public class ComPortComponent : ComPortToStream, IDisposable
    {
        FifoStream _DeviceViewStream;  // stream for reads and writes from device perspective
        Fifo<byte> ClrWriteFIFO;
        Fifo<byte> DeviceWriteFIFO;

        
        /// <summary>Standard Dispose support</summary>
        public void Dispose( )
        {
            Dispose( true );
            GC.SuppressFinalize(this);
        }

        /// <summary>Standard Dispose support</summary>
        /// <param name="Disposing">Flag to indicate if call is from dispose or finalization</param>
        protected virtual void Dispose( bool Disposing )
        {
            if( Disposing )
                this._DeviceViewStream.Dispose( );
        }

        /// <summary>Port Number for this serial port</summary>
        public int PortNumber
        {
            get { return base.ComPortHandle.PortNumber; }
            set
            {
                if( value > UInt16.MaxValue )
                    throw new ArgumentOutOfRangeException( "value" );

                // The port number is incremented by one here due to how
                // the emulator HAL looks up port numbers requested by the CLR.
                // Microsoft.SPOT.Emulator.Serial.SerialDriver.GetComPort()
                // always adds 1 to the port number when looking up a port for
                // reasons unknown.
                base.ComPortHandle = new ComPortHandle(TransportType.Usart, value +1);
            }
        }

        /// <summary>Size of the FIFO the managed application writes to and device reads from</summary>
        public int ClrWriteFifoSize
        {
            get { return this.ClrWriteFIFO.Capacity; }
            set
            {
                base.ThrowIfNotConfigurable( );
                this.ClrWriteFIFO = new Fifo<byte>( value );
            }
        }

        /// <summary>Size of the FIFO the managed application reads from and the device writes to</summary>
        public int DeviceWriteFifoSize
        {
            get { return this.DeviceWriteFIFO.Capacity; }
            set
            {
                base.ThrowIfNotConfigurable( );
                this.DeviceWriteFIFO = new Fifo<byte>( value );
            }
        }

        /// <summary>Initializes a new ComPortComponent with all fields having the default value</summary>
        /// <remarks>
        /// This constructor is needed to support creatin from the config XML file
        /// </remarks>
        public ComPortComponent( )
        {
        }

        /// <summary>Creates a new instance of a ComPortComponent</summary>
        /// <param name="ClrWriteFifoSize">Size of the FIFO the managed application writes to and the device reads from</param>
        /// <param name="DeviceWriteFifoSize">Size of the FIFO the managed application reads from and the device writes to</param>
        public ComPortComponent(int ClrWriteFifoSize, int DeviceWriteFifoSize )
        {
            this.ClrWriteFifoSize = ClrWriteFifoSize;
            this.DeviceWriteFifoSize = DeviceWriteFifoSize;
        }

        /// <summary>Creates a new instance of a ComPortComponent</summary>
        /// <param name="PortNumber">The Port Number</param>
        /// <param name="ClrWriteFifoSize">Size of the FIFO the managed application writes to and the device reads from</param>
        /// <param name="DeviceWriteFifoSize">Size of the FIFO the managed application reads from and the device writes to</param>
        public ComPortComponent(int PortNumber, int ClrWriteFifoSize, int DeviceWriteFifoSize )
            : this(ClrWriteFifoSize, DeviceWriteFifoSize)
        {
            this.PortNumber = PortNumber;
        }

        /// <summary>Performs allocation and initialization after configuration stage is completed</summary>
        public override void SetupComponent( )
        {
            base.SetupComponent( );
            
            // same FIFOs used for the 2 different "view" streams to
            // establish a sort of virtual NULL Modem cable between 
            // the CLR's view and that of the emulated device.

            // base class gets a stream for the CLR's view of the FIFOs
            FifoStream clrViewStream = new FifoStream( DeviceWriteFIFO, ClrWriteFIFO );
            clrViewStream.DataReceived += OnDataReceived;
            clrViewStream.Flushed += OnFlushed;
            base.Stream = clrViewStream;

            // emulated device gets the device view stream for the same FIFOs.
            this._DeviceViewStream = new FifoStream( ClrWriteFIFO, DeviceWriteFIFO );
        }

        void OnDataReceived(object sender, EventArgs e)
        {
            // ignore arguments and call the virtual func 
            // Hides internal FIFOs from derived classes
            OnDataReceived();
        }

        void OnFlushed(object sender, EventArgs e)
        {
            // ignore arguments and call the virtual func 
            // Hides internal FIFOs from derived classes
            OnFlush();
        }
        
        /// <summary>Function to handle data received from CLR</summary>
        /// <remarks>
        /// This is called once data is received from the CLR
        /// and is present in the FIFO stream for processing
        /// by the emulated device. 
        /// </remarks>
        protected virtual void OnDataReceived()
        {
        }

        /// <summary>Function to handle calls to Flush from CLR</summary>
        /// <remarks>
        /// This is called after the streams are already flushed and
        /// allows devices to reset any internal state based on the flush.
        /// While this is not needed in most cases it can be useful for
        /// emulation of specifc behavior in a particular type of UART. 
        /// </remarks>
        protected virtual void OnFlush( )
        {
        }

        /// <summary>override of base implementation to report proper amount of data available</summary>
        /// <remarks>
        /// Overriding this in derived classes should not be required and could cause confusion.
        /// </remarks>
        public override int AvailableBytes
        {
            get { return ((FifoStream)base.Stream).AvailableBytes; }
        }

        /// <summary>Stream for the Emulated device to use</summary>
        /// <remarks>
        /// Provides a stream from the devices view point.
        /// Writing to this stream stores data in the FIFO for
        /// the managed application to read. Reading from the
        /// stream retrieves data written to a FIFO by the managed
        /// application.
        /// </remarks>
        protected FifoStream DeviceViewStream
        {
            get { return this._DeviceViewStream; }
        }
    }
}
